Research
Security News
Quasar RAT Disguised as an npm Package for Detecting Vulnerabilities in Ethereum Smart Contracts
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Nodecaf is a framework on top of Express for building RESTful services in a quick and convenient manner.
Docs for version v0.9.x.
Nodecaf is an Express framework for developing REST APIs in a quick and convenient manner. Using Nodecaf you'll get:
If you are unfamiliar with Express, checkout their routing docs so that you can better grasp Nodecaf features and whatnot.
npm i -P -g nodecaf-cli
.package.json
).npm i -P nodecaf
.nodecaf init
.lib/main.js
const { AppServer } = require('nodecaf');
const api = require('./api');
module.exports = function init(){
let app = new AppServer();
// Expose things to all routes putting them in the 'shared' object.
let shared = {};
app.expose(shared);
// You can intercept all error that escape the route handlers.
app.onRuoteError = function(input, err, send){
// Any error that is not handled here will just become a harmless 500.
};
// Perform your server initialization logic.
app.beforeStart = async function(){
};
// Perform your server finalization logic.
app.afterStop = async function(){
};
// Load your routes and API definitions.
app.api(api);
// Don't forget to return your app.
return app;
}
lib/api.js
module.exports = function({ post, get, del, head, patch, put }){
// Use express routes and a list of functions (async or regular no matter).
get('/foo/:f/bar/:b', Foo.read, Bar.read);
post('/foo/:f/bar', Foo.read, Bar.write);
// ...
};
nodecaf run .
If you have found any problems with Nodecaf, please:
~bug
or ~security
.We will make sure to take a look when time allows us.
If you wish to get that awesome feature or have some advice for us, please:
~proposal
.If you have spotted any enhancements to be made and is willing to get your hands dirty about it, fork us and submit your merge request so we can collaborate effectively.
On top of all the cool features Express offers, check out how to use all the awesome goodies Nodecaf introduces.
In this manual we address as handler args the keys in the object passed as the only argument of any route handler function. The code below shows all handler args exposed by Nodecaf:
function({ req, res, next, query, params, body, flash, conf, log, error, headers }){
// Do your stuff.
}
Quick reference:
req
, res
, next
: The good old parameters used regularly in Express.query
, parameters
, body
, headers
: Shortcuts to the homonymous properties of req
.
They contain respectively the query string, the URL parameters, and the request
body data.flash
: Is a shortcut to Express req.locals
. Keys inserted in this a object
are preserved for the lifetime of a request and can be accessed in all handlers
of a route chain.conf
: This object contains the entire
application configuration data.log
: A logger instance. Use it to log events of
your application.error
: A function to output REST errors and abort the
handler chain execution.Nodecaf allow you to read a configuration file and use it's data in all routes and server configuration.
Use this feature to manage:
Suported config formats: TOML, YAML, JSON
Check out how to generate a project with configuration file already plugged in
To load a config file in your app, use the -c
flag through the CLI pointing
to your conf file path: nodecaf run -c my/conf/path.toml my/app
You can use the config data through it's handler arg in all route handlers as follows:
post('/foo', function({ conf }){
console.log(conf.key); //=> 'value'
});
Config data can also be passed as an object to the app constructor in lib/main.js
:
module.exports = function init(){
let conf = { key: 'value' };
let app = new AppServer(conf);
}
Or a file path if you want to have a fixed config file for setting defaults or any other reason:
module.exports = function init(){
let app = new AppServer(__dirname + '/default.toml');
}
You can also use the app.setup
to add a given configuration
file or object on top of the current one as follows:
app.setup('/path/to/settings.toml');
app.setup('/path/to/settings.yaml');
app.setup({ key: 'value' });
app.setup({ key: 'new-value', foo: 'bar' });
Layering is useful, for example, to keep a default settings file in your server source code to be overwritten by your user's.
Nodecaf logs events to stdout by default where each line of the ouput is a JSON object.
The log entries will have some default predefined values like pid, hostname etc...
In your route handlers, use the functions available in the log
object as follows:
function({ log }){
log.info('hi');
log.warn({lang: 'fr'}, 'au revoir');
log.fatal({ err: new Error() }, 'The error code is %d', 1234);
}
Below is described the signature of the available logging methods.
debug
, info
, warn
, error
, fatal
)Nodecaf will automatically log some useful server events as described in the table below:
Class | Level | Event |
---|---|---|
error after headers sent | warn | An error happened inside a route after the headers were already sent |
route error | error | An error happened inside a route and was not caught |
fatal error | fatal | An error happened that crashed the server process |
request | debug | A request has arrived |
response | debug | A response has been sent |
server | info | The server has started |
server | info | The server has stopped |
server | info | The server configuration has been reloaded |
websocket | debug | A new websocket connection happened |
websocket | debug | A message was received |
websocket | debug | A websocket connection was closed |
websocket | debug | Rejected a websocket connection to invalid path |
websocket | error | An error happened with a websocket connection |
Additionally, you can filter log entries by level and class with the following settings:
[log]
level = 'warn' # Only produce log entries with level 'warn' or higher ('error' & 'fatal')
class = 'my-class' # Only produce log entries with class matching exactly 'my-class'
Nodecaf brings the useful feature of accepting async functions as route handlers with zero configuration. All rejections/error within your async handler will be gracefully handled by the same routine the deals with regular functions. You will be able to avoid callback hell without creating bogus adapters for your promises.
get('/my/thing',
function({ res, next }){
res.send('My regular function works!');
next();
},
async function({ res }){
await myAsyncThing();
res.end('My async function works too!');s
}
);
In Nodecaf, any uncaught synchronous error happening inside route handler will be automatically converted into a harmless RESTful 500.
post('/my/thing', function(){
throw new Error('Should respond with a 500');
});
To support the callback error pattern, use the error
handler arg.
const fs = require('fs');
post('/my/thing', function({ error, res }){
fs.readFile('./my/file', 'utf8', function(err, contents){
if(err)
return error(err, 'Optional message to replace the original');
res.end(contents);
});
});
To use other HTTP status codes you can send a string in the first parameter of
error
. The supported error names are the following:
Error name | Status Code |
---|---|
NotFound | 404 |
Unauthorized | 401 |
ServerFault | 500 |
InvalidActionForState | 405 |
InvalidCredentials | 400 |
InvalidContent | 400 |
post('/my/thing', function({ error }){
try{
doThing();
}
catch(e){
error('NotFound', 'Optional message for the JSON response');
}
});
You can always deal with uncaught exceptions in all routes through a default
global error handler. In your lib/main.js
add an onRuoteError
function
property to the app
.
app.onRuoteError = function(input, err, send){
if(err instanceof MyDBError)
send('ServerFalut', 'Sorry! Database is sleeping.');
else if(err instanceof ValidationError)
send('InvalidContent', err.data);
}
send
function will instruct Nodecaf to output the given error.input
arg contain all handler args for the request.Error
the normal 500 behavior will
take place.Nodecaf provides you with an assertion module containing functions to generate the most common REST outputs based on some condition. Check an example to trigger a 404 in case a database record doesn't exist.
let { exist } = require('nodecaf').assertions;
get('/my/thing/:id', function({ params, db }){
let thing = await db.getById(params.id);
exist(thing, 'thing not found');
doStuff();
});
If the record is not found, the exist
call will stop the route execution right
away and generate a RESTful NotFound
error.
Along with exist
, the following assertions with similar behavior are provided:
Method | Error to be output |
---|---|
exist | NotFound |
valid | InvalidContent |
authorized | Unauthorized |
authn | InvalidCredentials |
able | InvalidActionForState |
To use it with callback style functions, pass the error
handler arg as the
third parameter.
let { exist } = require('nodecaf').assertions;
post('/my/file/:id', function({ error, res, params }){
fs.readFile('./my/file/' + params.id, 'utf8', function(err, contents){
exist(!err, 'File not found', error);
res.end(contents);
});
});
Nodecaf makes it simple to share global objects (eg.: database connections,
instanced libraries) across all route handlers. In your lib/main.js
you can
expose an object of which all keys will become handler args.
app.expose({
db: myDbConnection,
libX: new LibXInstance()
});
Then in all routes you can:
get('/my/thing', function({ db, libX }){
// use your global stuff
});
Nodecaf provides a setting to enable permissive CORS on all routes. Defaults to disabled. In your conf file:
cors = true
cors = 'my://origin'
cors = [ 'my://origin1', 'my://origin2' ]
Setup the cors according to the popular CORS Express middleware.
In production it's generally desirable to have an HTTPS setup for both client to
API and API to API communications. You can enable SSL for your server by adding
a ssl
key to your config, containing both the path for your key and cert.
[ssl]
key = "/path/to/key.pem"
cert = "/path/to/cert.pem"
When SSL is enabled the default server port becomes 443.
With nodecaf you can define paths to be accessible as WebSocket endpoints.
In your api file use the ws(path, events)
method with the folling arguments:
path
: where the websocket will be accessibleevents
: object containing any of the following handlers: connect
, message
, close
, error
.module.exports = function({ post, get, del, head, patch, put, ws }){
// Regular api routes...
// Websocket routes
ws('/my-path-1', {
connect: ({ client }) => console.log('NEW CLIENT'),
message: ({ message, client }) => console.log('NEW MESSAGE'),
close: ({ client }) => console.log('BYE CLIENT'),
error: ({ err, client }) => console.log('I FEEL ODD')
});
ws('/my-path-2', {
connect: ({ client }) => console.log('NEW CLIENT 2'),
message: ({ message, client }) => console.log('NEW MESSAGE 2'),
close: ({ client }) => console.log('BYE CLIENT 2'),
error: ({ err, client }) => console.log('I FEEL ODD 2')
});
};
All handlers are optional for each websocket endpoint.
Besides the ones showing, the following handler args are present in all ws handlers: client
, req
, flash
, conf
, log
, headers
, query
and exposed vars.
Nodecaf allow you to reject request bodies whose mime-type is not in a defined white-list. Denied requests will receive a 400 response with the apporpriate message.
Define a filter for the entire app on your api.js
:
module.exports = function({ }){
this.accept(['json', 'text/html']);
}
Override the global accept per route on your api.js
:
const { accept } = require('nodecaf');
module.exports = function({ post, put }){
// Define global accept rules
this.accept(['json', 'text/html']);
// Obtain accepts settings
let json = accept('json');
let img = accept([ 'png', 'jpg', 'svg', 'image/*' ]);
// Prepend accept definition in each route chain
post('/my/json/thing', json, myJSONHandler);
post('/my/img/thing', img, myImageHandler);
}
Nodecaf allows you to descibe your api and it's functionality, effectively turning your code in the single source of truth. The described API can later be used to generate an Open API compatible document.
In lib/api.js
describe your API as whole through the info
parameter:
module.exports = function({ get, info }){
info({
description: 'My awesome API that foos the bars and bazes the bahs'
});
get('/my/thing/:id', function(){
// ...
});
}
The info
funciton expects an object argument on the OpenAPI
Info Object
format. If not defined the title
and version
keys will default to your server's.
Describe your API endpoints by chaining a desc
method to each route definition.
module.exports = function({ get }){
get('/my/thing/:id', function(){
// ...
}).desc('Retrieves a thing from the database\n' +
`Searches the database for the thing with the given :id. Returns a
NotFound error in case no thing is found.`);
}
The desc
method takes a single string argument and uses it's first line (before \n
)
to set the
Operation object's
summary
property and the rest of the text to set the description
(CommonMark).
Property | Type | Description | Default |
---|---|---|---|
app.name | String | Name to be displayed in logs and documentation | 'Untitled' |
app.version | String | Verison to be displayed in logs and documentation | Version in Package JSON |
app.conf.delay | Integer | Milliseconds to wait before actually starting the app | 0 |
app.conf.port | Integer | Port for the web server to listen (also exposed as user conf) | 80 or 443 |
app.shouldParseBody | Boolean | Wether supported request body types should be parsed | true |
app.conf.formFileDir | Path | Where to store files uploaded as form-data | OS default temp dir |
app.alwaysRebuildAPI | Boolean | Wether the API should be rebuilt dynamically for every start or setup operation | false |
app.cookieSecret | String | A secure random string to be used for signing cookies | none |
[v0.9.5] (hotfix) - 2021-01-13
FAQs
Nodecaf is a light framework for developing RESTful Apps in a quick and convenient manner.
The npm package nodecaf receives a total of 567 weekly downloads. As such, nodecaf popularity was classified as not popular.
We found that nodecaf demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Research
Security News
Socket researchers uncover a malicious npm package posing as a tool for detecting vulnerabilities in Etherium smart contracts.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.